import Instructor from "@instructor-ai/instructor";
import OpenAI from "openai";
import { z } from "zod";
import * as path from "path";
import { dbFolder } from "./util";
import * as fs from "fs-extra";
import kebabCase from "lodash.kebabcase";

const GH_DESC_CHAR_LIMIT = 350;
const DescriptionSchema = z.object({ description: z.string().max(350) });
type Description = z.infer<typeof DescriptionSchema>;
const openai = new OpenAI({ apiKey: process.env["OPENAI_API_KEY"] });
const client = Instructor({
  client: openai,
  mode: "FUNCTIONS",
});

export const processedCustomRequestCacheDir = path.join(
  dbFolder,
  "generate-repository-description-cache"
);

export async function generateRepositoryDescription(
  companyName: string,
  serviceName: string | undefined,
  metaDescription: string
): Promise<string> {
  const cachePath = path.join(
    processedCustomRequestCacheDir,
    `${kebabCase(`${companyName}${serviceName ? `-${serviceName}` : ""}`)}.json`
  );
  if (fs.existsSync(cachePath)) {
    const cache: Record<string, string> = JSON.parse(
      fs.readFileSync(cachePath, "utf-8")
    );
    const cached = cache[metaDescription];
    if (cached) return cached;
  }

  const serviceNameSuffix = serviceName ? `for ${serviceName} API ` : "";
  let description = metaDescription.trim().replace(/\n/g, " ");
  if (!description.endsWith(".")) description = description + ".";
  const konfigDescription = `${companyName}'s {language} SDK ${serviceNameSuffix}generated by Konfig (https://konfigthis.com/).`;

  if (description.length > GH_DESC_CHAR_LIMIT)
    description = await shortenDescriptionWithAI(description);
  const concatted = `${description} ${konfigDescription}`;
  let result = concatted.length > GH_DESC_CHAR_LIMIT ? description : concatted;

  if (!fs.existsSync(processedCustomRequestCacheDir)) {
    fs.mkdirSync(processedCustomRequestCacheDir, { recursive: true });
  }
  if (!fs.existsSync(cachePath)) {
    fs.writeFileSync(
      cachePath,
      JSON.stringify({ [metaDescription]: result }, null, 2)
    );
  } else {
    const cache: Record<string, string> = JSON.parse(
      fs.readFileSync(cachePath, "utf-8")
    );
    cache[metaDescription] = result;
    fs.writeFileSync(cachePath, JSON.stringify(cache, null, 2));
  }
  return result;
}

async function shortenDescriptionWithAI(description: string): Promise<string> {
  const prompt = generatePrompt(description);
  const response = await client.chat.completions.create({
    messages: [{ role: "user", content: prompt }],
    model: "gpt-3.5-turbo",
    response_model: { schema: DescriptionSchema, name: "Description" },
  });

  if (response === null)
    throw Error(
      "Null response from OpenAI when shortening repository description."
    );
  if (response.description.length > 350)
    throw Error(
      `OpenAI failed to shorten response to less than 350 characters: ${response}`
    );
  return response.description;
}

function generatePrompt(description: string): string {
  const prompt = `Shorten this company description to be less than 300 characters.
Respond only with the shortened description and nothing else. Your entire response MUST BE LESS THAN 300 CHARACTERS.

${description}`;
  return prompt;
}
